home *** CD-ROM | disk | FTP | other *** search
Wrap
/* File: QTEffects.c Contains: Code to generate a QuickTime movie with a QuickTime video effect in it. Written by: Scott Kuechle (based heavily on QuickTime SDK QTShowEffect sample code by Tim Monroe) Copyright: © 1999 by Apple Computer, Inc. All rights reserved Change History (most recent first) <1> 9/25/99 srk first file <2> 10/19/99 srk changed cross fade effect to explode effect This file defines functions that create a QuickTime movie with a video effect as a transition from one picture to another. This project differs from the QTShowEffect sample code it is based on in that it allows the user to create the effect "by hand" e.g. without the aid of the standard QuickTime effects parameter dialog box. Instead, the program itself builds the correct effects parameter description atoms. Here's a quick rundown of how the program works: • First, a QuickTime movie with two video tracks is created - these two tracks will be used as sources for a explode video effect • The effect description and sample description structures for the explode effect are created • An effects track and media are created and added to our movie for the explode effect • The input map for the effect is created, and references for the two video tracks are added to this input map The resulting movie, when played, will show a smooth alpha blending between the two video tracks. */ #ifndef __IMAGECODEC__ #include <ImageCodec.h> #endif #ifndef __MOVIES__ #include <Movies.h> #endif #ifndef __MOVIESFORMAT__ #include <MoviesFormat.h> #endif #ifndef __MEDIAHANDLERS__ #include <MediaHandlers.h> #endif #ifndef __NUMBERFORMATTING__ #include <NumberFormatting.h> #endif #ifndef __QUICKTIMECOMPONENTS__ #include <QuickTimeComponents.h> #endif #ifndef __ENDIAN__ #include <Endian.h> #endif #ifndef __GESTALT__ #include <Gestalt.h> #endif #ifndef __FIXMATH__ #include <FixMath.h> #endif #ifndef __MACWINDOWS__ #include <MacWindows.h> #endif #ifndef __PRINTING__ #include <Printing.h> #endif #ifndef __SCRIPT__ #include <Script.h> #endif #ifndef __SOUND__ #include <Sound.h> #endif #ifndef _STRING_H #include <string.h> #endif #ifndef _STDLIB_H #include <stdlib.h> #endif #ifndef __TEXTUTILS__ #include <TextUtils.h> #endif #ifndef __TRAPS__ #include <Traps.h> #endif #ifndef __RESOURCES__ #include <Resources.h> #endif #ifndef __FILETYPESANDCREATORS__ #include <FileTypesAndCreators.h> #endif #if TARGET_OS_WIN32 // Windows headers #define STRICT #include <windows.h> // QTML stuff #include "QTML.h" #endif #include "QTEffects.h" // miscellaneous constants #define kSourceOneName FOUR_CHAR_CODE('srcA') #define kSourceTwoName FOUR_CHAR_CODE('srcB') #define kSourceNoneName FOUR_CHAR_CODE('srcZ') #define kPictGWorldDepth 32 #define kPict1ResID 128 #define kPict2ResID 129 #define kPrompt "Specify a new movie file name:" #define kMovieFileName "Movie File.mov" #define kOneSecond 600 #define kEffectMovieDuration (3 * kOneSecond) #define kTrackDisplayWidth 200 #define kTrackDisplayHeight 240 #define FAIL_OSERR(y) if (y != noErr) {goto bail;} //////////////////// // // QTEffects_GetPictResourceAsGWorld // Create a new GWorld of the specified size and bit depth; then draw the specified PICT resource into it. // The new GWorld is returned through the theGW parameter. // //////////////////// Movie QTEffects_CreateEffectsMovie() { Movie theMovie = nil; short resRefNum = 0; short resId = movieInDataForkResID; OSErr myErr = noErr; Track sourceTrack1, sourceTrack2; ImageDescriptionHandle mySampleDesc = NULL; QTAtomContainer theEffectDesc; Track myTrack; Media myMedia; FSSpec movieFSSpec; /* Let's first create a movie file with two video tracks. Each video track will be used as a source for our explode effect */ QTEffects_CreateTwoTrackMovie( &theMovie, &resRefNum, &movieFSSpec, &sourceTrack1, &sourceTrack2); /* Setup the effect description and sample description for our cross fade effect */ QTEffects_SetupEffectsDescription( kExplodeTransitionType/*kCrossFadeTransitionType*/, &mySampleDesc, &theEffectDesc); /* Create and add an effects track and media to our movie for the cross fade effect */ QTEffects_CreateEffectsTrackAndMedia( theMovie, mySampleDesc, theEffectDesc, &myTrack, &myMedia); /* Create the input map for the effect, and add references for the two video tracks */ QTEffects_CreateInputMapAndAddTrackReferences( myTrack, myMedia, sourceTrack1, sourceTrack2); /* save our changes to the movie */ myErr = AddMovieResource (theMovie, resRefNum, &resId, nil); DisposeMovie(theMovie); if (mySampleDesc != NULL) DisposeHandle((Handle)mySampleDesc); return theMovie; } //////////////////// // // QTEffects_CreateTwoTrackMovie // // Create a movie with two video tracks. Each track is based on our stored picture files. // The two tracks will be used as sources for our two-source explode effect. // //////////////////// static void QTEffects_CreateTwoTrackMovie( Movie *theMovie, short *resRefNum, FSSpec *movieFSSpec, Track *videoTrack1, Track *videoTrack2) { Point where = {100,100}; SFReply theSFReply; OSErr myErr = noErr; GWorldPtr offscreenGWorld1,offscreenGWorld2; /* Let's first create a movie file */ SFPutFile (where, c2pstr(kPrompt), c2pstr(kMovieFileName), nil, &theSFReply); if (!theSFReply.good) return; FSMakeFSSpec(theSFReply.vRefNum, 0, theSFReply.fName, movieFSSpec); myErr = CreateMovieFile (movieFSSpec, sigMoviePlayer, smCurrentScript, createMovieFileDeleteCurFile | createMovieFileDontCreateResFile, resRefNum, theMovie); FAIL_OSERR(myErr); /* Now we'll retrieve some stored pictures which will be used in the creation of our video tracks */ myErr = QTEffects_GetPictResourceAsGWorld (kPict1ResID, kTrackDisplayWidth, kTrackDisplayHeight, kPictGWorldDepth, &offscreenGWorld1); FAIL_OSERR(myErr); myErr = QTEffects_GetPictResourceAsGWorld (kPict2ResID, kTrackDisplayWidth, kTrackDisplayHeight, kPictGWorldDepth, &offscreenGWorld2); FAIL_OSERR(myErr); /* Now add two video tracks to our movie, based on the source pictures just retrieved. Note: video tracks used as sources for an effect should start at the same time as the effect track and end at the same time as the effect track */ myErr = QTEffects_AddVideoTrackFromGWorld(theMovie, offscreenGWorld1, videoTrack1, 0, kTrackDisplayWidth, kTrackDisplayHeight); FAIL_OSERR(myErr); myErr = QTEffects_AddVideoTrackFromGWorld(theMovie, offscreenGWorld2, videoTrack2, 0, kTrackDisplayWidth, kTrackDisplayHeight); FAIL_OSERR(myErr); bail: ; } //////////////////// // // QTEffects_GetPictResourceAsGWorld // Create a new GWorld of the specified size and bit depth; then draw the specified PICT resource into it. // The new GWorld is returned through the theGW parameter. // //////////////////// static OSErr QTEffects_GetPictResourceAsGWorld (short theResID, short theWidth, short theHeight, short theDepth, GWorldPtr *theGW) { PicHandle myHandle = NULL; PixMapHandle myPixMap = NULL; CGrafPtr mySavedPort; GDHandle mySavedDevice; Rect myRect; OSErr myErr = noErr; // get the current drawing environment GetGWorld(&mySavedPort, &mySavedDevice); // read the specified PICT resource from the application's resource file myHandle = GetPicture(theResID); if (myHandle == NULL) { myErr = ResError(); if (myErr == noErr) myErr = resNotFound; goto bail; } // set the size of the GWorld MacSetRect(&myRect, 0, 0, theWidth, theHeight); // allocate a new GWorld myErr = QTNewGWorld(theGW, theDepth, &myRect, NULL, NULL, kICMTempThenAppMemory); FAIL_OSERR(myErr); SetGWorld(*theGW, NULL); // get a handle to the offscreen pixel image and lock it myPixMap = GetGWorldPixMap(*theGW); LockPixels(myPixMap); EraseRect(&myRect); DrawPicture(myHandle, &myRect); if (myPixMap != NULL) UnlockPixels(myPixMap); bail: // restore the previous port and device SetGWorld(mySavedPort, mySavedDevice); if (myHandle != NULL) ReleaseResource((Handle)myHandle); return(myErr); } //////////////////// // // QTEffects_AddVideoTrackFromGWorld // Add to the specified movie a video track for the specified picture resource. // //////////////////// static OSErr QTEffects_AddVideoTrackFromGWorld (Movie *theMovie, GWorldPtr theGW, Track *theSourceTrack, long theStartTime, short theWidth, short theHeight) { Media myMedia; ImageDescriptionHandle myDesc = NULL; Rect myRect; long mySize; Handle myData = NULL; Ptr myDataPtr = NULL; GWorldPtr myGWorld = NULL; CGrafPtr mySavedPort = NULL; GDHandle mySavedGDevice = NULL; PicHandle myHandle = NULL; PixMapHandle mySrcPixMap = NULL; PixMapHandle myDstPixMap = NULL; OSErr myErr = noErr; // get the current port and device GetGWorld(&mySavedPort, &mySavedGDevice); // create a video track in the movie *theSourceTrack = NewMovieTrack(*theMovie, FixRatio(theWidth, 1), FixRatio(theHeight, 1), kNoVolume); myMedia = NewTrackMedia(*theSourceTrack, VideoMediaType, kOneSecond, NULL, 0); // get the rectangle for the movie GetMovieBox(*theMovie, &myRect); // begin editing the new track BeginMediaEdits(myMedia); // create a new GWorld; we draw the picture into this GWorld and then compress it // (note that we are creating a picture with the maximum bit depth) myErr = NewGWorld(&myGWorld, kPictGWorldDepth, &myRect, NULL, NULL, 0L); FAIL_OSERR(myErr); mySrcPixMap = GetGWorldPixMap(theGW); // LockPixels(mySrcPixMap); myDstPixMap = GetGWorldPixMap(myGWorld); LockPixels(myDstPixMap); // create a new image description; CompressImage will fill in the fields of this structure myDesc = (ImageDescriptionHandle)NewHandle(4); SetGWorld(myGWorld, NULL); // copy the image from the specified GWorld into the new GWorld CopyBits((BitMap *)(*mySrcPixMap), (BitMap *)(*myDstPixMap), &(theGW->portRect), &(myGWorld->portRect), srcCopy, 0L); // restore the original port and device SetGWorld(mySavedPort, mySavedGDevice); myErr = GetMaxCompressionSize(myDstPixMap, &myRect, 0, codecNormalQuality, kAnimationCodecType, anyCodec, &mySize); FAIL_OSERR(myErr); myData = NewHandle(mySize); FAIL_OSERR(myErr); HLockHi(myData); myDataPtr = StripAddress(*myData); myErr = CompressImage(myDstPixMap, &myRect, codecNormalQuality, kAnimationCodecType, myDesc, myDataPtr); FAIL_OSERR(myErr); myErr = AddMediaSample(myMedia, myData, 0, (**myDesc).dataSize, kEffectMovieDuration, (SampleDescriptionHandle)myDesc, 1, 0, NULL); FAIL_OSERR(myErr); myErr = EndMediaEdits(myMedia); FAIL_OSERR(myErr); myErr = InsertMediaIntoTrack(*theSourceTrack, theStartTime, 0, GetMediaDuration(myMedia), fixed1); bail: // restore the original port and device SetGWorld(mySavedPort, mySavedGDevice); if (myData != NULL) { HUnlock(myData); DisposeHandle(myData); } if (myDesc != NULL) DisposeHandle((Handle)myDesc); // if (mySrcPixMap != NULL) // UnlockPixels(mySrcPixMap); if (myDstPixMap != NULL) UnlockPixels(myDstPixMap); if (myGWorld != NULL) DisposeGWorld(myGWorld); return(myErr); } //////////////////// // // QTEffects_SetupEffectsDescription // Create the effect description and sample description for the effect. // //////////////////// static void QTEffects_SetupEffectsDescription( OSType theEffectType, ImageDescriptionHandle *mySampleDesc, QTAtomContainer *theEffectDesc) { // set up a new effect description QTEffects_CreateEffectDescription(theEffectType, kSourceOneName, kSourceTwoName, theEffectDesc); // set up the effects parameters for the explode effect QTEffects_CreateEffectParameterForExplode(*theEffectDesc); // create an effect sample description *mySampleDesc = QTEffects_MakeSampleDescription(theEffectType, kTrackDisplayWidth, kTrackDisplayHeight); } //////////////////// // // QTEffects_CreateEffectsTrackAndMedia // Create an effects track and associated media for our movie. // //////////////////// static void QTEffects_CreateEffectsTrackAndMedia(Movie myMovie, ImageDescriptionHandle mySampleDesc, QTAtomContainer theEffectDesc, Track *myTrack, Media *myMedia) { OSErr myErr; TimeValue mySampleTime; *myTrack = NewMovieTrack(myMovie, FixRatio(kTrackDisplayWidth, 1), FixRatio(kTrackDisplayHeight, 1), kNoVolume); myErr = GetMoviesError(); FAIL_OSERR(myErr); *myMedia = NewTrackMedia(*myTrack, VideoMediaType, kOneSecond, NULL, 0); myErr = GetMoviesError(); FAIL_OSERR(myErr); // add the effect description as a sample to the effect track media myErr = BeginMediaEdits(*myMedia); FAIL_OSERR(myErr); myErr = AddMediaSample(*myMedia, (Handle)theEffectDesc, 0, GetHandleSize((Handle)theEffectDesc), kEffectMovieDuration, (SampleDescriptionHandle)mySampleDesc, 1, 0, &mySampleTime); FAIL_OSERR(myErr); myErr = EndMediaEdits(*myMedia); FAIL_OSERR(myErr); // add the media to the track myErr = InsertMediaIntoTrack(*myTrack, 0, mySampleTime, GetMediaDuration(*myMedia), fixed1); FAIL_OSERR(myErr); bail: ; } //////////////////// // // QTEffects_CreateInputMapAndAddTrackReferences // Create an input map, and add references to the input map for both // video tracks. // //////////////////// static void QTEffects_CreateInputMapAndAddTrackReferences(Track effectsTrack, Media effectsTrackMedia, Track sourceTrack1, Track sourceTrack2) { QTAtomContainer myInputMap; OSErr myErr; // create the input map myErr = QTNewAtomContainer(&myInputMap); FAIL_OSERR(myErr); // add references for the two video tracks myErr = QTEffects_AddTrackReferenceToInputMap(myInputMap, effectsTrack, sourceTrack1, kSourceOneName); FAIL_OSERR(myErr); // try using existing video track as source myErr = QTEffects_AddTrackReferenceToInputMap(myInputMap, effectsTrack, sourceTrack2, kSourceTwoName); FAIL_OSERR(myErr); // add the input map to the effects track myErr = SetMediaInputMap(effectsTrackMedia, myInputMap); FAIL_OSERR(myErr); bail: ; if (myInputMap != NULL) QTDisposeAtomContainer(myInputMap); } //////////////////// // // QTEffects_CreateEffectDescription // Create an effect description for zero, one, or two sources. // // The effect description specifies which video effect is desired and the parameters for that effect. // It also describes the source(s) for the effect. An effect description is simply an atom container // that holds atoms with the appropriate information. // // Note that because we are creating an atom container, we must pass big-endian data (hence the calls // to EndianU32_NtoB). // // The caller is responsible for disposing of the returned atom container, by calling QTDisposeAtomContainer. // //////////////////// static void QTEffects_CreateEffectDescription (OSType theEffectName, OSType theSourceName1, OSType theSourceName2, QTAtomContainer *theEffectDesc) { OSType myType; OSErr myErr = noErr; // create a new, empty effect description myErr = QTNewAtomContainer(theEffectDesc); FAIL_OSERR(myErr); // create the effect ID atom: the atom type is kParameterWhatName, and the atom ID is kParameterWhatID myType = EndianU32_NtoB(theEffectName); myErr = QTInsertChild(*theEffectDesc, kParentAtomIsContainer, kParameterWhatName, kParameterWhatID, 0, sizeof(myType), &myType, NULL); FAIL_OSERR(myErr); // add the first source, if it's not kSourceNoneName if (theSourceName1 != kSourceNoneName) { myType = EndianU32_NtoB(theSourceName1); myErr = QTInsertChild(*theEffectDesc, kParentAtomIsContainer, kEffectSourceName, 1, 0, sizeof(myType), &myType, NULL); FAIL_OSERR(myErr); } // add the second source, if it's not kSourceNoneName if (theSourceName2 != kSourceNoneName) { myType = EndianU32_NtoB(theSourceName2); myErr = QTInsertChild(*theEffectDesc, kParentAtomIsContainer, kEffectSourceName, 2, 0, sizeof(myType), &myType, NULL); } bail: ; } //////////////////// // // QTEffects_CreateEffectParameterForExplode // Create the parameter atoms for the Explode effect. This will include a // percentage atom, along with a tween atom to tween the effect, plus atoms // specifying the x & y coordinates of the explosion centre. // //////////////////// static void QTEffects_CreateEffectParameterForExplode(QTAtomContainer myEffectDesc) { Fixed percentage[2]; Fixed explodeCentreX,explodeCentreY; OSErr myErr = noErr; QTAtom newAtom; OSType tweenType; /* add the percentage ('pcnt') atom for the effect */ myErr = QTInsertChild(myEffectDesc, kParentAtomIsContainer, kParameterUsagePercent, 1, 0, 0, 0, &newAtom); FAIL_OSERR(myErr); /* this atom specifies the format of the tween data */ tweenType = EndianU32_NtoB(kParameterTypeDataFixed); myErr = QTInsertChild(myEffectDesc, newAtom, kTweenType, 1 , 0, sizeof(OSType), &tweenType, NULL); FAIL_OSERR(myErr); /* here's the data for the tween */ percentage[0] = EndianS32_NtoB( X2Fix(0.90) ); /* 90% */ percentage[1] = EndianS32_NtoB( X2Fix(0.10) ); /* 20% */ myErr = QTInsertChild(myEffectDesc, newAtom, kTweenData, 1 , 0, sizeof(long) * 2, &percentage, NULL); /* Add Explode X Centre atom */ explodeCentreX = EndianS32_NtoB( X2Fix(0.0) ); myErr = QTInsertChild(myEffectDesc, kParentAtomIsContainer, 'xcnt', 1, 0, sizeof(long), &explodeCentreX, 0); FAIL_OSERR(myErr); /* Add Explode Y Centre atom */ explodeCentreY = EndianS32_NtoB( X2Fix(0.0) ); myErr = QTInsertChild(myEffectDesc, kParentAtomIsContainer, 'ycnt', 1, 0, sizeof(long), &explodeCentreY, 0); FAIL_OSERR(myErr); bail: ; } //////////////////// // // QTEffects_MakeSampleDescription // Return a new image description with default and specified values. // //////////////////// static ImageDescriptionHandle QTEffects_MakeSampleDescription (OSType theEffectType, short theWidth, short theHeight) { ImageDescriptionHandle mySampleDesc = NULL; // create a new sample description mySampleDesc = (ImageDescriptionHandle)NewHandleClear(sizeof(ImageDescription)); if (mySampleDesc == NULL) return(NULL); // fill in the fields of the sample description (**mySampleDesc).idSize = sizeof(ImageDescription); (**mySampleDesc).cType = theEffectType; (**mySampleDesc).vendor = kAppleManufacturer; (**mySampleDesc).temporalQuality = codecNormalQuality; (**mySampleDesc).spatialQuality = codecNormalQuality; (**mySampleDesc).width = theWidth; (**mySampleDesc).height = theHeight; (**mySampleDesc).hRes = 72L << 16; (**mySampleDesc).vRes = 72L << 16; (**mySampleDesc).frameCount = 1; (**mySampleDesc).depth = 0; (**mySampleDesc).clutID = -1; return(mySampleDesc); } //////////////////// // // QTEffects_AddTrackReferenceToInputMap // Add a track reference to the specified input map. // //////////////////// static OSErr QTEffects_AddTrackReferenceToInputMap (QTAtomContainer theInputMap, Track theTrack, Track theSrcTrack, OSType theSrcName) { OSErr myErr = noErr; QTAtom myInputAtom; long myRefIndex; OSType myType; myErr = AddTrackReference(theTrack, theSrcTrack, kTrackModifierReference, &myRefIndex); FAIL_OSERR(myErr); // add a reference atom to the input map myErr = QTInsertChild(theInputMap, kParentAtomIsContainer, kTrackModifierInput, myRefIndex, 0, 0, NULL, &myInputAtom); FAIL_OSERR(myErr); // add two child atoms to the parent reference atom myType = EndianU32_NtoB(kTrackModifierTypeImage); myErr = QTInsertChild(theInputMap, myInputAtom, kTrackModifierType, 1, 0, sizeof(myType), &myType, NULL); FAIL_OSERR(myErr); myType = EndianU32_NtoB(theSrcName); myErr = QTInsertChild(theInputMap, myInputAtom, kEffectDataSourceType, 1, 0, sizeof(myType), &myType, NULL); bail: return(myErr); }